using System;
using System.Collections.Generic;
using System.Linq;
using MathSharp;
using MathSharp.Utils;

namespace MathSharp.Script
{
	/// <summary>
	/// Сущность для описания параметров вычисления на различных этапах
	/// </summary>
	public class EvolutionContext
	{
		/// <summary>
		/// Групировка измерений по типам
		/// </summary>
		public enum DimensionType {
			/// <summary>
			/// глобальное и постоянное измерение для всего вычисления. к.прим. измерение id расчёта
			/// </summary>
			Global = 1,
			/// <summary>
			/// Измерение изменяемое для всех расчётов для отдельных итераций. к.прим. объект расчёта, время
			/// </summary>
			Evolution = 2,
			/// <summary>
			/// Измерение существующее тольок в пределах одного выражения
			/// </summary>
			Expression = 3
		}
		
		Dictionary<string, DimensionType> dimensions = new Dictionary<string, DimensionType>();
		Dictionary<string, Value> dimensionValues = new Dictionary<string, Value>();
		EvolutionContext parent;
		List<EvolutionContext> childs = new List<EvolutionContext>();
		List<VariableExpression> nestedVariableExpressions = new List<VaribleExpression>();
		List<FunctionExpression> nestedFunctionExpressions = new List<FunctionExpression>();
		
		protected EvolutionContext (EvolutionContext parentContext)
		{
			parent = parentContext;
			parent.childs.Add(this);
		}     
		
		public EvolutionContext(): this(null) {
		}
		
		/// <summary>
		/// Создаёт дочерний контекст с родительской привязкой к текущему
		/// </summary>
		public EvolutionContext Fork() {
			return new EvolutionContext(this);
		}
		
		#region Dimensions
		/// <summary>
		/// Пытается найти и вернуть тип измерения по имени
		/// </summary>
		public bool TryGetDimension(string dimension, out DimensionType dType) {
			if (dimensions.TryGetValue(dimension, dType))
				return true;
			else if (parent != null && parent.TryGetDimension(dimension, dType))
				return true;
			else
				return false;
		}
		
		/// <summary>
		/// Устанавливает тип измерения
		/// </summary>
		protected bool SetDimensionType(string dimension, DimensionType dType) {
			if (dimensions.Contains(dimension)) {
				dimensions[dimension] = dType;
				return true;
			} else if (parent != null) {
				return parent.SetDimensionType(dimension, dType);
			} else
				return false;
		}
		
		/// <summary>
		/// Добавляет новое измерение на текущем уровне вычислений
		/// </summary>
		/// <param name="dType">
		/// Предлагаемый тип измерения
		/// </param>
		/// <returns>
		/// Установленый тип измерения
		/// </returns>
		public DimensionType AddDimensionByType(string dimension, DimensionType dType) {
			DimensionType res;
			if (TryGetDimension(dimension, out res)) {
				// такое измерение уже занесенно. Проверняем не надо ли повысить его тип
				if (res > dType) {
					SetDimensionType(dimension, dType);
					res = dType;
				}
			} else {
				dimensions.Add(dimension, dType);
				res = dType;
			}
			return dType;
		}
		
		/// <summary>
		/// Добавляет измерение уровня Expression
		/// </summary>
		public DimensionType AddDimensionFromExpression(string dimension) {
			return AddDimensionByType(dimension, DimensionType.Expression);
		}
		
		/// <summary>
		/// Добавляет измерение уровня Evolution
		/// </summary>
		public DimensionType AddDimensionForEvolution(string dimension) {
			return AddDimensionByType(dimension, DimensionType.Evolution);
		}
		
		/// <summary>
		/// Добавляет измерение уровня Global
		/// </summary>
		public DimensionType AddDimensionGlobal(string dimension) {
			return AddDimensionByType(dimension, DimensionType.Global);
		}
		
		/// <summary>
		/// Проверяет наличие измерения в контекстах
		/// </summary>
		/// <param name="allowParent">
		/// Если заданно true, то проверка выполняется и в родительских контекстах
		/// </param>
		public bool ContainsDimension(string dimension, bool allowParent) {
			if (dimensions.ContainsKey(dimension))
				return true;
			if (allowParent && parent != null && parent.ContainsDimension(dimension, allowParent))
				return true;
			return false;
		}
		#endregion
		
		#region Dimension values
		protected void _setDimensionValue(string dimension, Value val, bool allowSetParent) {
			if (dimensions.ContainsKey(dimension)) { // значение измерения на данном уровне
				if (!dimensionValues.ContainsKey(dimension))
					dimensionValue.Add(dimension, val);
				else
					dimensionValues[dimension] = val;
			} else if (allowSetParent && parent != null) { // значчение измерения у парента
				parent._setDimensionValue(dimension, val, allowSetParent);
			} else { // ошибка целостности
				throw new Exception("Ошибка целостности: попутка установить значение измерению, которе не существует");
			}
		}
		
		/// <summary>
		/// Устанавливает значение для измерения
		/// </summary>
		/// <param name="allowSetParent">
		/// Если заданно true, то допускает установку значения у родительских контекстов, если в текущем контексте измерение не найденно
		/// </param>
		public void SetDimensionValue(string dimension, Value val, bool allowSetParent) {
			if (!ContainsDimension(dimension, allowSetParent))
				throw new Exception("Невозможно установить значение измерению {0} т.к. это измерение небыло объявленно".format(dimension));
			_setDimensionValue(dimension, val, allowSetParent);
		}
		#endregion
		
		// необходимость данного блока в данный момент под вопросмо
		#region Nested variable expressions
		/*
		/// <summary>
		/// Проверяет наличие выражения переменной в таблице зависимостей
		/// </summary>
		public bool ContaintNestedVariableExpression(VariableExpression v) {
			if (nestedVariables.Contains(v))
				return true;
			if (parent != null && parent.ContaintNestedVariable(v))
				return true;
			return false;
		}
		
		/// <summary>
		/// Добавляет выражение переменной в таблицу зависимостей
		/// </summary>
		public void AddNestedVariableExpression(VariableExpression v) {
			if (ContaintNestedVariable(v))
				return;
			nestedVariables.Add(v);
		}
		
		/// <summary>
		/// Возвращает полный перечень всех выражений переменных для вычисления текущего контекста с учётом родительских контекстов
		/// </summary>
		public IEnumerable<VariableExpression> GetAllNestedVariableExpressions() {
			IEnumerable<VariableExpression> res = nestedVariables;
			if (parent != null)
				res = res.Union(parent.GetAllNestedVariables());
			return res;
		}*/
		#endregion
	}
}

